當需要多個物件同時「監聽」一個相同物件,一旦該物件發生變化時,監聽的物件們將自動採取相對應的處理。
通常是發生在單個物件的狀態變化發生時,其他物件將會動態地跟著調整、執行各自對應的行為。
在模式內是這樣稱呼的:
作法是:
interface
,使用 update()
作為共同方法。interface
,會建立三個常用的方法:
add()
。remove()
。notify()
。update()
的細節。notify()
要思考的是,通知者因為哪些行為改變自身狀態後才需要「通知」觀察者。以下範例以「簡易判斷 Log 等級後發出警示」為核心製作。
製作 Log 資料:Log
public class Log {
private String title;
private String level;
private String message;
private String type;
public Log(String title, String level, String message, String type) {
this.title = title;
this.level = level;
this.message = message;
this.type = type;
}
public String getTitle() {
return title;
}
public String getLevel() {
return level;
}
public String getMessage() {
return title + ", " + message;
}
public String getType() {
return type;
}
}
製作觀察者的虛擬層親代:Siren
public interface Siren {
void update(String logLevel, String message);
}
製作通知者的虛擬層親代:LogCollectorMethods
public interface LogCollectorMethods {
void addSubject(String subject);
void addSubscriber(String subject, Siren siren);
void removeSubscriber(String subject, Siren siren);
void notifySubscribers(String subject, String level, String message);
}
製作觀察者子代:ApiPassFailedSiren
、LogInFailedSiren
(Observer 物件)
public class ApiPassFailedSiren implements Siren {
private String name;
private String level;
public ApiPassFailedSiren(String name, String level) {
this.name = name;
this.level = level;
}
@Override
public void update(String logLevel, String message) {
if (level.equals("low")) {
alert(message);
} else if (level.equals("medium")) {
if (logLevel.equals("high") || logLevel.equals("medium")) {
alert(message);
}
} else if (level.equals("high") && logLevel.equals("high")) {
alert(message);
}
}
private void alert(String message) {
System.out.println("The Siren: '" + name + "' is working now, and the message is:\n" + message);
}
}
public class LogInFailedSiren implements Siren {
private String name;
private String level;
public LogInFailedSiren(String name, String level) {
this.name = name;
this.level = level;
}
@Override
public void update(String logLevel, String message) {
if (level.equals("low")) {
alert(message);
} else if (level.equals("medium")) {
if (logLevel.equals("high") || logLevel.equals("medium")) {
alert(message);
}
} else if (level.equals("high") && logLevel.equals("high")) {
alert(message);
}
}
private void alert(String message) {
System.out.println("The 'Log In Failed' " + name + " Siren is working now, and the message is:\n" + message);
}
}
製作通知者子代:ApiPassFailedSiren
(Subject 物件)
public class LogCollector implements LogCollectorMethods {
private Map<String, List<Siren>> subscribers;
public LogCollector() {
this.subscribers = new HashMap<>();
}
@Override
public void addSubject(String subject) {
if (!subscribers.containsKey(subject)) {
subscribers.put(subject, new ArrayList<>());
}
}
@Override
public void addSubscriber(String subject, Siren siren) {
if (!subscribers.containsKey(subject)) {
subscribers.put(subject, new ArrayList<>());
}
subscribers.get(subject).add(siren);
}
@Override
public void removeSubscriber(String subject, Siren siren) {
List<Siren> sirens = subscribers.get(subject);
sirens.remove(siren);
}
@Override
public void notifySubscribers(String subject, String level, String message) {
List<Siren> sirens = subscribers.get(subject);
for (Siren siren : sirens) {
siren.update(level, message);
}
System.out.println("");
}
public void receiveLog(Log log) {
notifySubscribers(log.getType(), log.getLevel(), log.getMessage());
}
}
測試,建立通知者後,依序註冊觀察者,最後試著發送訊息:LogCollectorObserverSample
public class LogCollectorObserverSample {
public static void main(String[] args) {
LogCollector logCollector = new LogCollector();
logCollector.addSubject("api");
logCollector.addSubscriber("api", new ApiPassFailedSiren("/users", "low"));
logCollector.addSubscriber("api", new ApiPassFailedSiren("/photos", "medium"));
logCollector.addSubscriber("api", new ApiPassFailedSiren("/cars", "low"));
logCollector.addSubscriber("api", new ApiPassFailedSiren("/keys", "high"));
logCollector.addSubject("logIn");
logCollector.addSubscriber("logIn", new LogInFailedSiren("users", "high"));
logCollector.addSubscriber("logIn", new LogInFailedSiren("admin", "low"));
logCollector.receiveLog(new Log("api pass failed", "high", "the client uses wrong token", "api"));
logCollector.receiveLog(new Log("log in failed", "low", "the account is wrong", "logIn"));
}
}
製作 Log 資料:Log
class Log {
/**
* @param {string} title
* @param {string} level
* @param {string} message
* @param {string} type
*/
constructor(title, level, message, type) {
this.title = title;
this.level = level;
this.message = message;
this.type = type;
}
getTitle() {
return this.title;
}
getLevel() {
return this.level;
}
getMessage() {
return this.title + ", " + this.message;
}
getType() {
return this.type;
}
}
製作觀察者的虛擬層親代:Siren
/** @abstract */
class Siren {
/**
* @param {string} name
* @param {string} level
*/
constructor(name, level) {
this.name = name;
this.level = level;
}
/**
* @override
* @param {string} logLevel
* @param {string} message
*/
update(logLevel, message) {
if (this.level === "low") {
this.alert(message);
} else if (this.level === "medium") {
if (logLevel === "high" || logLevel === "medium") {
this.alert(message);
}
} else if (this.level === "high" && logLevel === "high") {
this.alert(message);
}
}
/** @param {string} message */
alert(message) { return; }
/** @returns {string} */
getName() { return; }
}
製作通知者的虛擬層親代:LogCollectorMethods
/** @abstract */
class LogCollectorMethods {
constructor() {
/** @type {Map<String, Siren[]>} */
this.subscribers = new Map();
}
/** @param {string} subject */
addSubject(subject) { return; }
/**
* @param {string} subject
* @param {Siren} siren
*/
addSubscriber(subject, siren) { return; }
/**
* @param {string} subject
* @param {Siren} siren
*/
removeSubscriber(subject, siren) { return; }
/**
* @param {string} subject
* @param {string} level
* @param {string} message
*/
notifySubscribers(subject, level, message) { return; }
}
製作觀察者子代:ApiPassFailedSiren
、LogInFailedSiren
(Observer 物件)
class ApiPassFailedSiren extends Siren {
/**
* @param {string} name
* @param {string} level
*/
constructor(name, level) {
super(name, level);
}
/**
* @override
* @param {string} message
*/
alert(message) {
console.log("The Siren: '" + this.name + "' is working now, and the message is:\n" + message);
}
/** @override */
getName() {
return this.name;
}
}
class LogInFailedSiren extends Siren {
/**
* @param {string} name
* @param {string} level
*/
constructor(name, level) {
super(name, level);
}
/**
* @override
* @param {string} message
*/
alert(message) {
console.log("The 'Log In Failed' " + this.name + " Siren is working now, and the message is:\n" + message);
}
/** @override */
getName() {
return this.name;
}
}
製作通知者子代:ApiPassFailedSiren
(Subject 物件)
class LogCollector extends LogCollectorMethods {
constructor() {
super();
}
/**
* @override
* @param {string} subject
*/
addSubject(subject) {
if (!this.subscribers.has(subject)) {
this.subscribers.set(subject, []);
}
}
/**
* @override
* @param {string} subject
* @param {Siren} siren
*/
addSubscriber(subject, siren) {
if (!this.subscribers.has(subject)) {
this.subscribers.set(subject, []);
}
this.subscribers.get(subject).push(siren);
}
/**
* @override
* @param {string} subject
* @param {Siren} siren
*/
removeSubscriber(subject, siren) {
const sirens = this.subscribers.get(subject);
const removeSpecificSiren = sirens.filter(item => item.getName() !== siren.getName())
this.subscribers.set(subject, removeSpecificSiren);
}
/**
* @override
* @param {string} subject
* @param {string} level
* @param {string} message
*/
notifySubscribers(subject, level, message) {
const sirens = this.subscribers.get(subject);
for (const siren of sirens) {
siren.update(level, message);
}
console.log("");
}
/** @param {Log} log */
receiveLog(log) {
this.notifySubscribers(log.getType(), log.getLevel(), log.getMessage());
}
}
測試,建立通知者後,依序註冊觀察者,最後試著發送訊息:logCollectorObserverSample
const logCollectorObserverSample = () => {
const logCollector = new LogCollector();
logCollector.addSubject("api");
logCollector.addSubscriber("api", new ApiPassFailedSiren("/users", "low"));
logCollector.addSubscriber("api", new ApiPassFailedSiren("/photos", "medium"));
logCollector.addSubscriber("api", new ApiPassFailedSiren("/cars", "low"));
logCollector.addSubscriber("api", new ApiPassFailedSiren("/keys", "high"));
logCollector.addSubject("logIn");
logCollector.addSubscriber("logIn", new LogInFailedSiren("users", "high"));
logCollector.addSubscriber("logIn", new LogInFailedSiren("admin", "low"));
logCollector.receiveLog(new Log("api pass failed", "high", "the client uses wrong token", "api"));
logCollector.receiveLog(new Log("log in failed", "low", "the account is wrong", "logIn"));
};
logCollectorObserverSample();
Observer 模式還有另一個稱呼:Publish/Subscribe,這稱呼直接了當地表達該模式的重點。
自己實作時觀察到:
delegate
可以簡化。最後要提一點,Observer 模式與 Mediator 模式,兩者的初衷不同,前者是建立單向的連結,後者是消除物件們複雜的依賴關係,而實作騎來卻十分相似,想想這或許是讓程式碼維持高彈性、減少複雜度等目的後,必然的結果也說不定。
明天將介紹 Behavioural patterns 的第八個模式:State 模式。